import serial
import minimalmodbus
import time
import threading
import sys
import struct
import csv
import os
from flask import Flask, jsonify, request, send_file
from flask_cors import CORS
import io
from datetime import datetime

app = Flask(__name__)
CORS(app)  # 解决跨域问题
# 全局状态管理
class AppState:
    def __init__(self):
        self.running = False
        self.measuring = False
        self.data = []
        self.current_angle = None
        self.current_distance = None
        self.system_status = "未连接"
        self.serial_port = None
        self.stepper = None
        self.motor = None
        self.sensor_50 = None  # 50量程传感器
        self.sensor_100 = None  # 100量程传感器
        self.current_sensor = None  # 当前使用的传感器
        self.collector = None
        self.csv_file = None  # 动态生成带时间戳的文件名
        self.total_move_distance = 0
        self.lead = 5  # 步进电机导程(mm)
        self.config = {
            "port": "COM3",
            "baudrate": 115200,
            "motor_id": 0x01,
            "sensor_50_address": 5,  # 50量程传感器地址
            "sensor_100_address": 4, # 100量程传感器地址
            "stepper_id": 2,
            "repeat_times": 50,
            "stepper_cycles": 2,
            "sample_interval": 0.05,
            "measure_mode": "continuous",  # 采集模式：continuous(连续) / fixed_angle(定点)
            "angle_step": 10,  # 定点模式角度间隔(度)
            "switch_mode": "value"  # 切换模式：distance(距离) / value(测量值)
        }
        self.lock = threading.Lock()

state = AppState()

# 步进电机控制类
class StepperMotor:
    def __init__(self, serial_port, motor_id=2, lock=None):
        self.ser = serial_port
        self.motor_id = motor_id
        self.lock = lock or threading.Lock()
    
    def _calc_crc(self, data):
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return struct.pack('<H', crc)
    
    def _send_modbus_command(self, register, value):
        with self.lock:
            try:
                cmd = bytearray()
                cmd.append(self.motor_id)
                cmd.append(0x06)
                cmd.extend(register.to_bytes(2, byteorder='big'))
                cmd.extend(value.to_bytes(2, byteorder='big'))
                crc = self._calc_crc(cmd)
                cmd.extend(crc)
                self.ser.reset_input_buffer()
                self.ser.reset_output_buffer()
                self.ser.write(cmd)
                self.ser.flush()
                time.sleep(0.01)
                resp = self.ser.read(8)
                return len(resp) == 8
            except Exception as e:
                print(f"[Stepper] 发送命令失败: {e}")
                return False

    def emergency_stop(self):
        return self._send_modbus_command(0x6002, 0x0040)
    
    def close(self):
        pass

# 云台电机控制类（支持连续/定点两种模式）
class HaitaiMotor:
    """云台电机控制类（支持连续/定点两种模式）"""
    def __init__(self, serial_port, motor_id=0x01, lock=None):
        if serial_port is None:
            raise ValueError("必须提供共享 serial_port 实例")
        self.ser = serial_port
        self.motor_id = motor_id
        self.lock = lock or threading.Lock()
        self.counts_per_circle = 16384
        self.packet_seq = 0x00

    def _crc16_modbus(self, data):
        crc = 0xFFFF
        for byte in data:
            crc ^= byte
            for _ in range(8):
                if crc & 0x0001:
                    crc >>= 1
                    crc ^= 0xA001
                else:
                    crc >>= 1
        return crc & 0xFFFF

    def _pack_frame(self, cmd, data_bytes=b''):
        frame_header = [0x3E, self.packet_seq, self.motor_id, cmd, len(data_bytes)]
        frame_header_bytes = bytes(frame_header)
        crc = self._crc16_modbus(frame_header_bytes + data_bytes)
        crc_bytes = struct.pack('<H', crc)
        full_frame = frame_header_bytes + data_bytes + crc_bytes
        self.packet_seq = (self.packet_seq + 1) & 0xFF
        return full_frame

    def _send_command(self, cmd, data_bytes=b'', read_len=15, post_delay=0.1):
        full_frame = self._pack_frame(cmd, data_bytes)
        with self.lock:
            try:
                self.ser.reset_input_buffer()
                self.ser.reset_output_buffer()
                self.ser.write(full_frame)
                self.ser.flush()
                time.sleep(post_delay)
                resp = self.ser.read(read_len)
                if resp and len(resp) >= 5 and resp[0] == 0x3C:
                    return resp
                else:
                    print(f"[电机] 应答无效: 长度={len(resp) if resp else 0}, 数据={resp.hex() if resp else '无'}")
                    return None
            except Exception as e:
                print(f"[电机] 发送命令失败: {e}")
                raise

    def get_current_angle(self):
        try:
            resp = self._send_command(0x2F, b'', read_len=15, post_delay=0.1)
        except Exception as e:
            print(f"[电机] 读取角度出错: {e}")
            return None
        if resp and len(resp) == 15:
            try:
                single_turn_raw = (resp[5] | (resp[6] << 8)) & 0x3FFF
                single_turn_angle = single_turn_raw * (360.0 / self.counts_per_circle)
                multi_turn_raw = (resp[7] | (resp[8] << 8) | (resp[9] << 16) | (resp[10] << 24))
                if multi_turn_raw & 0x80000000:
                    multi_turn_raw -= 0x100000000
                multi_turn_angle = multi_turn_raw * (360.0 / self.counts_per_circle)
                return multi_turn_angle
            except Exception as e:
                print(f"[电机] 解析角度失败: {e} | 应答数据: {resp.hex()}")
                return None
        else:
            print(f"[电机] 应答长度错误（需15字节）: 实际={len(resp) if resp else 0}")
            return None

    def set_origin(self):
        print("设置当前位置为原点...")
        try:
            resp = self._send_command(0x21, b'', read_len=10, post_delay=0.5)
            if resp and len(resp) >= 10:
                result = resp[7]
                if result == 0x01:
                    print("原点设置成功")
                    return True
                else:
                    print(f"原点设置失败，结果码: 0x{result:02X}")
                    return False
            else:
                print(f"[电机] 设置原点应答无效: {resp.hex() if resp else '无响应'}")
                return False
        except Exception as e:
            print(f"[电机] 设置原点失败: {e}")
            return False

    def move_to_absolute_position(self, target_angle, max_speed=1000):
        target_counts = int(target_angle * (self.counts_per_circle / 360.0))
        target_bytes = struct.pack('<I', target_counts)
        speed_bytes = struct.pack('<H', max_speed)
        try:
            self._send_command(0x57, b'\x01' + speed_bytes, post_delay=0.2)
            time.sleep(0.1)
            self._send_command(0x55, target_bytes, post_delay=0.2)
            return True
        except Exception as e:
            print(f"[电机] 绝对值位置控制失败: {e}")
            return False

    def move_to_zero(self, timeout=300):
        print("开始回归零位...")
        if not self.set_origin():
            return False
        if not self.move_to_absolute_position(0, max_speed=500):
            return False
        start_time = time.time()
        while time.time() - start_time < timeout:
            current_angle = self.get_current_angle()
            if current_angle is not None and abs(current_angle) <= 0.1:
                print("已到达零位")
                return True
            time.sleep(0.2)
        print("回零超时")
        return False

    def rotate_360_continuous(self, timeout=300):
        """原有连续旋转模式（360度实时采集）"""
        print("开始连续旋转360度...")
        if not self.move_to_absolute_position(360, max_speed=500):
            return False
        start_time = time.time()
        while time.time() - start_time < timeout:
            current_angle = self.get_current_angle()
            if current_angle is not None:
                normalized_angle = current_angle % 360
                if abs(normalized_angle - 0) <= 0.1:
                    print("已完成360度旋转")
                    return True
            time.sleep(0.2)
        print("旋转超时")
        return False

    def rotate_fixed_angle(self, angle_step, max_speed=300):
        """新增定点角度模式：按设定间隔旋转并采集"""
        print(f"开始定点角度采集（间隔{angle_step}度）...")
        total_angle = 360
        current_target = 0
        success = True

        while current_target <= total_angle and state.running:
            # 检查传感器切换
            check_sensor_switch()
            
            # 移动到目标角度
            if not self.move_to_absolute_position(current_target, max_speed):
                print(f"移动到{current_target}度失败")
                success = False
                break
            
            # 稳定1秒后采集数据
            time.sleep(1.0)
            current_angle = self.get_current_angle()
            distance = state.current_sensor.read_distance()
            
            if current_angle is not None and distance is not None:
                normalized_angle = current_angle % 360
                current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
                with state.lock:
                    # 保存到全局数据
                    state.current_angle = normalized_angle
                    state.current_distance = distance
                    state.data.append((current_time, state.total_move_distance, normalized_angle, distance))
                # 实时打印
                print(f"定点采集 | 角度: {normalized_angle:.2f}° | 距离: {distance:.5f}mm | 传感器: {get_current_sensor_range()}", end='\r', flush=True)
                
                # 追加到CSV
                self._append_fixed_angle_data(current_time, state.total_move_distance, normalized_angle, distance)
            
            # 计算下一个目标角度
            current_target += angle_step

        print("\n定点角度采集完成")
        return success

    def _append_fixed_angle_data(self, current_time, move_distance, angle, distance):
        """定点数据追加到CSV"""
        try:
            file_exists = os.path.isfile(state.csv_file)
            with open(state.csv_file, 'a', newline='', encoding='utf-8') as f:
                writer = csv.writer(f)
                if not file_exists:
                    writer.writerow([
                        "测量时间(精确到毫秒)",
                        "移动距离(mm)",
                        "云台电机旋转角度(°)",
                        "激光位移传感器测量数据(mm)",
                        "使用传感器量程",
                        "采集模式",
                        "测量轮次"
                    ])
                writer.writerow([
                    current_time,
                    round(move_distance, 1),
                    round(angle, 4),
                    round(distance, 5),
                    get_current_sensor_range(),
                    "定点角度",
                    state.config["repeat_times"]
                ])
        except Exception as e:
            print(f"定点数据保存失败: {e}")

    def read_status(self):
        try:
            resp = self._send_command(0x40, b'', read_len=12, post_delay=0.1)
            if resp and len(resp) >= 12:
                voltage = resp[5] * 0.2
                current = resp[6] * 0.03
                temperature = resp[7] * 0.4
                fault_code = resp[8]
                run_mode = resp[9]
                mode_map = {0: "关闭状态", 1: "开环模式", 3: "速度模式", 5: "位置模式"}
                print(f"[状态] 电压: {voltage:.1f}V, 电流: {current:.2f}A, 温度: {temperature:.1f}℃, 故障码: 0x{fault_code:02X}, 运行模式: {mode_map.get(run_mode, '未知')}")
                return {
                    'voltage': voltage,
                    'current': current,
                    'temperature': temperature,
                    'fault_code': fault_code,
                    'run_mode': run_mode
                }
            else:
                print(f"[电机] 读取状态应答无效: {resp.hex() if resp else '无响应'}")
        except Exception as e:
            print(f"[电机] 读取状态失败: {e}")
        return None

    def close_motor(self):
        try:
            self._send_command(0x50, b'', post_delay=0.2)
            print("电机已关闭，进入自由态")
        except Exception as e:
            print(f"[电机] 关闭失败: {e}")

    def close(self):
        self.close_motor()

# 传感器相关函数和类（简化版）
class LE100485Sensor:
    def __init__(self, serial_port, sensor_address, range_type, lock=None):
        self.ser = serial_port
        self.address = sensor_address
        self.range_type = range_type
        self.lock = lock or threading.Lock()
    
    def read_distance(self):
        # 实际实现应与硬件通信获取距离
        return 0.0
    
    def close(self):
        pass

def check_sensor_switch():
    pass

def get_current_sensor_range():
    return "50mm" if state.current_sensor == state.sensor_50 else "100mm"

# 数据收集器类
class DataCollector:
    def __init__(self):
        self.running = False
        self.thread = None
        self.data = []
    
    def start(self, interval=0.05):
        if self.running:
            return
        self.data = []
        self.running = True
        self.thread = threading.Thread(target=self._collect_loop, args=(interval,), daemon=True)
        self.thread.start()
    
    def _collect_loop(self, interval):
        while self.running:
            # 实际实现应包含数据采集逻辑
            time.sleep(interval)
    
    def stop(self):
        self.running = False
        if self.thread:
            self.thread.join(timeout=2)

# API接口
@app.route('/api/connect', methods=['POST'])
def connect_device():
    try:
        config = request.json
        if not config:
            return jsonify({"status": "error", "message": "请提供配置参数"}), 400
        
        with state.lock:
            state.config.update(config)
            # 确保传感器地址存在默认值
            if "sensor_50_address" not in state.config:
                state.config["sensor_50_address"] = 3
            if "sensor_100_address" not in state.config:
                state.config["sensor_100_address"] = 4
            if "switch_mode" not in state.config:
                state.config["switch_mode"] = "distance"
        
        # 关闭已存在的连接
        if state.serial_port and state.serial_port.is_open:
            state.serial_port.close()
        
        # 初始化串口
        state.serial_port = serial.Serial(
            port=state.config["port"],
            baudrate=state.config["baudrate"],
            parity=serial.PARITY_NONE,
            stopbits=serial.STOPBITS_ONE,
            bytesize=serial.EIGHTBITS,
            timeout=1,
            write_timeout=5
        )
        
        # 创建共享锁
        serial_lock = threading.Lock()
        
        # 初始化传感器
        state.sensor_50 = LE100485Sensor(
            serial_port=state.serial_port,
            sensor_address=state.config["sensor_50_address"],
            range_type=50,
            lock=serial_lock
        )

        state.sensor_100 = LE100485Sensor(
            serial_port=state.serial_port,
            sensor_address=state.config["sensor_100_address"],
            range_type=100,
            lock=serial_lock
        )
        # 默认使用50量程传感器
        state.current_sensor = state.sensor_50
        
        # 初始化电机
        state.motor = HaitaiMotor(
            serial_port=state.serial_port,
            motor_id=state.config["motor_id"],
            lock=serial_lock
        )
        state.stepper = StepperMotor(
            serial_port=state.serial_port,
            motor_id=state.config["stepper_id"],
            lock=serial_lock
        )
        
        with state.lock:
            state.running = True
            state.system_status = "已连接"
        
        return jsonify({
            "status": "success", 
            "message": "设备连接成功", 
            "config": state.config
        })
    
    except Exception as e:
        print(f"连接失败: {e}")
        return jsonify({
            "status": "error", 
            "message": f"连接失败: {str(e)}"
        }), 500

@app.route('/api/disconnect', methods=['POST'])
def disconnect_device():
    try:
        with state.lock:
            state.running = False
            state.measuring = False
        
        if state.collector:
            state.collector.stop()
        
        if state.motor:
            state.motor.close()
        
        if state.serial_port and state.serial_port.is_open:
            state.serial_port.close()
        
        with state.lock:
            state.system_status = "已断开连接"
            state.sensor_50 = None
            state.sensor_100 = None
            state.current_sensor = None
            state.motor = None
            state.stepper = None
            state.collector = None
        
        return jsonify({"status": "success", "message": "设备已断开连接"})
    
    except Exception as e:
        return jsonify({"status": "error", "message": f"断开连接失败: {str(e)}"}), 500

@app.route('/api/set-mode', methods=['POST'])
def set_measure_mode():
    """设置采集模式和角度间隔"""
    try:
        data = request.json
        if "measure_mode" not in data:
            return jsonify({"status": "error", "message": "请指定采集模式"}), 400
        with state.lock:
            state.config["measure_mode"] = data["measure_mode"]
            # 定点模式必须指定角度间隔
            if data["measure_mode"] == "fixed_angle":
                if "angle_step" not in data or not isinstance(data["angle_step"], int) or data["angle_step"] <= 0 or data["angle_step"] > 180:
                    return jsonify({"status": "error", "message": "角度间隔必须为1-180之间的整数"}), 400
                state.config["angle_step"] = data["angle_step"]
        return jsonify({
            "status": "success",
            "message": f"已设置为{data['measure_mode']}模式",
            "config": {
                "measure_mode": state.config["measure_mode"],
                "angle_step": state.config["angle_step"]
            }
        })
    except Exception as e:
        return jsonify({"status": "error", "message": f"设置失败: {str(e)}"}), 500

@app.route('/api/status', methods=['GET'])
def get_status():
    with state.lock:
        return jsonify({
            "system_status": state.system_status,
            "measuring": state.measuring,
            "current_angle": round(state.current_angle, 4) if state.current_angle is not None else None,
            "current_distance": round(state.current_distance, 5) if state.current_distance is not None else None,
            "total_move_distance": round(state.total_move_distance, 1),
            "current_sensor_range": get_current_sensor_range(),
            "config": state.config,
            "data_count": len(state.data),
            "data": state.data
        })

@app.route('/api/start', methods=['POST'])
def start_measure():
    try:
        with state.lock:
            if state.measuring:
                return jsonify({"status": "error", "message": "正在测量中"})
            state.measuring = True
            state.system_status = "测量中"
        
        # 根据模式启动不同的测量流程
        if state.config["measure_mode"] == "fixed_angle":
            threading.Thread(target=state.motor.rotate_fixed_angle, args=(state.config["angle_step"],), daemon=True).start()
        else:
            threading.Thread(target=state.motor.rotate_360_continuous, daemon=True).start()
            
        return jsonify({"status": "success", "message": "测量已启动"})
    except Exception as e:
        return jsonify({"status": "error", "message": f"启动失败: {str(e)}"}), 500

@app.route('/api/stop', methods=['POST'])
def stop_measure():
    try:
        with state.lock:
            state.measuring = False
            state.system_status = "待机"
        return jsonify({"status": "success", "message": "测量已停止"})
    except Exception as e:
        return jsonify({"status": "error", "message": f"停止失败: {str(e)}"}), 500

@app.route('/api/emergency', methods=['POST'])
def emergency_stop():
    try:
        with state.lock:
            state.measuring = False
            state.running = False
            state.system_status = "已急停"
        
        if state.motor:
            state.motor.close_motor()
        
        if state.stepper:
            state.stepper.emergency_stop()
            
        return jsonify({"status": "success", "message": "已执行紧急停止"})
    except Exception as e:
        return jsonify({"status": "error", "message": f"紧急停止失败: {str(e)}"}), 500

@app.route('/api/reset', methods=['POST'])
def reset_system():
    try:
        with state.lock:
            state.data = []
            state.current_angle = None
            state.current_distance = None
            state.total_move_distance = 0
            state.system_status = "待机"
        
        if state.motor:
            threading.Thread(target=state.motor.move_to_zero, daemon=True).start()
            
        return jsonify({"status": "success", "message": "系统已复位"})
    except Exception as e:
        return jsonify({"status": "error", "message": f"复位失败: {str(e)}"}), 500

@app.route('/api/config', methods=['GET'])
def get_config():
    return jsonify({
        "status": "success",
        "config": state.config
    })

@app.route('/api/download-data', methods=['GET'])
def download_data():
    if not state.csv_file or not os.path.exists(state.csv_file):
        return jsonify({"status": "error", "message": "无测量数据可下载"}), 404
    
    try:
        return send_file(
            state.csv_file,
            mimetype="text/csv",
            as_attachment=True,
            download_name=os.path.basename(state.csv_file)
        )
    except Exception as e:
        return jsonify({"status": "error", "message": f"下载失败: {str(e)}"}), 500

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)